﻿using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;

using Tomahawk.Runtime.Logic;
using Tomahawk.Runtime.Physics.Interfaces;
using Tomahawk.Runtime.Physics.Descriptors;

namespace Tomahawk.Runtime.Physics
{
    public class PhysicsManager : IDisposable
    {
        #region Properties
        private IPhysicsScene scene = null;
        public IPhysicsScene Scene
        {
            get { return scene; }
        }
        #endregion

        public PhysicsManager()
        {
            PhysicsSceneDesc sceneDesc = new PhysicsSceneDesc();
            Engine.Instance.PhysService.CreateScene(sceneDesc, out scene);
        }

        public virtual void Dispose()
        {
            scene.Dispose();
        }

        #region XActor linkage
        private Dictionary<XCollisionComponent, IPhysicsActor> linkedActors = new Dictionary<XCollisionComponent, IPhysicsActor>();

        public void Link(XCollisionComponent actor, IPhysicsActor physActor)
        {
            physActor.Data = actor;
            linkedActors.Add(actor, physActor);
        }

        public void Unlink(XCollisionComponent actor)
        {            
            IPhysicsActor physActor = null;
            if (linkedActors.TryGetValue(actor, out physActor))
            {
                physActor.Data = null;
            }

            linkedActors.Remove(actor);
        }
        #endregion

        #region Physics Simulation
        public void Simulate(float elapsedTime)
        {
            scene.Simulate(elapsedTime);
        }
        public void WaitForResults()
        {
            List<Collision> collisions = null;
            List<Contact> contacts = null;
            scene.WaitForResults(out collisions, out contacts);

            foreach (Collision collision in collisions)
            {
                XObject obj0 = collision.actor0.Data;
                XObject obj1 = collision.actor1.Data;

                if (obj0 is XCollisionComponent && obj1 is XCollisionComponent)
                {
                    XCollisionComponent cmp0 = (XCollisionComponent)obj0;
                    XCollisionComponent cmp1 = (XCollisionComponent)obj1;

                    cmp0.NotifyCollision(cmp1);
                    cmp1.NotifyCollision(cmp0);
                }
            }

            foreach (Contact contact in contacts)
            {
                XObject trigger = contact.trigger.Data;
                XObject actor = contact.actor.Data;

                if (trigger is XCollisionComponent && actor is XCollisionComponent)
                {
                    XCollisionComponent cmp0 = (XCollisionComponent)trigger;
                    XCollisionComponent cmp1 = (XCollisionComponent)actor;

                    cmp0.NotifyContact(cmp1, contact.type);
                }
            }
        }

        public void UpdateWorld()
        {
            foreach (KeyValuePair<XCollisionComponent, IPhysicsActor> pair in linkedActors)
            {
                XActor actor = pair.Key.Owner;
                IPhysicsActor physActor = pair.Value;

                switch (physActor.PhysicsType)
                {
                    case PhysicsActorType.StaticActor:
                        {
                            // Do nothing
                        }
                        break;
                    case PhysicsActorType.TriggerActor:
                    case PhysicsActorType.InterpActor:
                        {
                            Vector3 position = actor.SceneComponent.SceneNode.Position;
                            Quaternion rotation = actor.SceneComponent.SceneNode.Rotation;

                            physActor.MoveTo("root", position, rotation);
                        }
                        break;
                    case PhysicsActorType.DynamicActor:
                        {
                            Vector3 position = physActor.GetLocation("root");
                            Quaternion rotation = physActor.GetRotation("root");

                            actor.SetLocation(position);
                            actor.SetRotation(rotation);
                        }
                        break;
                }
            }
        }
        #endregion
    }
}
